#pragma once

#include <boost/pfr.hpp>

#include <algorithm>
#include <type_traits>

#include <utils/Math.h>
#include <utils/concepts/Common.h>

namespace solar::internal::vector
{
    template <std::size_t N, Struct T1, Struct T2, typename Func>
        requires(N <= 0)
    constexpr void forEach(T1&& vec1, T2&& vec2, Func&& func)
    {
        // Empty function
    }

    template <std::size_t N, Struct T1, Struct T2, typename Func>
    constexpr void forEach(T1&& vec1, T2&& vec2, Func&& func)
    {
        auto& val1 = boost::pfr::get<N - 1>(vec1);
        auto& val2 = boost::pfr::get<N - 1>(vec2);
        func(val1, val2);
        forEach<N - 1>(vec1, vec2, func);
    }

    template <std::size_t N, Struct T1, Struct T2, Struct T3, typename Func>
        requires(N <= 0)
    constexpr void forEach(T1&& vec1, T2&& vec2, T3&& vec3, Func&& func)
    {
        // Empty function
    }

    template <std::size_t N, Struct T1, Struct T2, Struct T3, typename Func>
    constexpr void forEach(T1&& vec1, T2&& vec2, T3&& vec3, Func&& func)
    {
        auto& val1 = boost::pfr::get<N - 1>(vec1);
        auto& val2 = boost::pfr::get<N - 1>(vec2);
        auto& val3 = boost::pfr::get<N - 1>(vec3);
        func(val1, val2, val3);
        forEach<N - 1>(vec1, vec2, vec3, func);
    }

    template <std::size_t N, Struct T1, Struct T2, Struct T3, Struct T4, typename Func>
        requires(N <= 0)
    constexpr void forEach(T1&& vec1, T2&& vec2, T3&& vec3, T4&& vec4, Func&& func)
    {
        // Empty function
    }

    template <std::size_t N, Struct T1, Struct T2, Struct T3, Struct T4, typename Func>
    constexpr void forEach(T1&& vec1, T2&& vec2, T3&& vec3, T4&& vec4, Func&& func)
    {
        auto& val1 = boost::pfr::get<N - 1>(vec1);
        auto& val2 = boost::pfr::get<N - 1>(vec2);
        auto& val3 = boost::pfr::get<N - 1>(vec3);
        auto& val4 = boost::pfr::get<N - 1>(vec4);
        func(val1, val2, val3, val4);
        forEach<N - 1>(vec1, vec2, vec3, vec4, func);
    }

    template <Struct T> constexpr auto negate(T&& vec) -> std::remove_cvref_t<T>
    {
        using RawT = std::remove_cvref_t<T>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec, ret, [](auto& field, auto& ret_field)
                                                { ret_field = -field; });
        return ret;
    }

    template <Struct T1, Struct T2>
    constexpr auto plus(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = field1 + field2; });
        return ret;
    }

    template <Struct T1, Number T2>
    constexpr auto plus(T1&& vec1, T2&& num) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, ret, [num](auto& field, auto& ret_field)
                                                { ret_field = field + num; });
        return ret;
    }

    template <Struct T1, Struct T2> constexpr auto plusSelf(T1&& vec1, T2&& vec2) -> void
    {
        using RawT = std::remove_cvref_t<T1>;
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, [](auto& field1, auto& field2)
                                                { field1 += field2; });
    }

    template <Struct T1, Number T2> constexpr auto plusSelf(T1&& vec1, T2&& num) -> void
    {
        boost::pfr::for_each_field(vec1, [num](auto& field1) { field1 += num; });
    }

    template <Struct T1, Struct T2>
    constexpr auto minus(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = field1 - field2; });
        return ret;
    }

    template <Struct T1, Number T2>
    constexpr auto minus(T1&& vec1, T2&& num) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, ret, [num](auto& field, auto& ret_field)
                                                { ret_field = field - num; });
        return ret;
    }

    template <Struct T1, Struct T2> constexpr auto minusSelf(T1&& vec1, T2&& vec2) -> void
    {
        using RawT = std::remove_cvref_t<T1>;
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, [](auto& field1, auto& field2)
                                                { field1 -= field2; });
    }

    template <Struct T1, Number T2> constexpr auto minusSelf(T1&& vec1, T2&& num) -> void
    {
        boost::pfr::for_each_field(vec1, [num](auto& field1) { field1 -= num; });
    }

    template <Struct T1, Struct T2>
    constexpr auto multiply(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = field1 * field2; });
        return ret;
    }

    template <Struct T1, Number T2>
    constexpr auto multiply(T1&& vec1, T2&& num) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, ret, [num](auto& field1, auto& ret_field)
                                                { ret_field = field1 * num; });
        return ret;
    }

    template <Struct T1, Struct T2> constexpr auto multiplySelf(T1&& vec1, T2&& vec2) -> void
    {
        using RawT = std::remove_cvref_t<T1>;
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, [](auto& field1, auto& field2)
                                                { field1 *= field2; });
    }

    template <Struct T1, Number T2> constexpr auto multiplySelf(T1&& vec1, T2&& num) -> void
    {
        boost::pfr::for_each_field(vec1, [num](auto& field1) { field1 *= num; });
    }

    template <Struct T1, Struct T2>
    constexpr auto divide(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = field1 / field2; });
        return ret;
    }

    template <Struct T1, Number T2>
    constexpr auto divide(T1&& vec1, T2&& num) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, ret, [num](auto& field1, auto& ret_field)
                                                { ret_field = field1 / num; });
        return ret;
    }

    template <Struct T1, Struct T2> constexpr auto divideSelf(T1&& vec1, T2&& vec2) -> void
    {
        using RawT = std::remove_cvref_t<T1>;
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, [](auto& field1, auto& field2)
                                                { field1 /= field2; });
    }

    template <Struct T1, Number T2> constexpr auto divideSelf(T1&& vec1, T2&& num) -> void
    {
        boost::pfr::for_each_field(vec1, [num](auto& field1) { field1 /= num; });
    }

    template <Struct T1, Struct T2, Number T3>
    constexpr auto lerp(T1&& vec1, T2&& vec2, T3&& num) -> std::remove_cvref_t<T1>
    {
        // return a + t * (b - a);
        return plus(vec1, multiply(negate(minus(vec1, vec2)), num));
    }

    template <Struct T1, Number T2, Number T3>
    constexpr auto clamp(T1&& vec, T2&& low, T3&& high) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec, ret, [low, high](auto& field1, auto& ret_field)
                                                { ret_field = std::clamp(field1, low, high); });
        return ret;
    }

    template <Struct T1, Struct T2, Struct T3>
    constexpr auto clamp(T1&& vec, T2&& low, T3&& high) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(
            vec, low, high, ret,
            [](auto& field1, auto& low_field, auto& high_field, auto& ret_field)
            { ret_field = std::clamp(field1, low_field, high_field); });
        return ret;
    }

    template <Struct T1, Struct T2> constexpr auto dot(T1&& vec1, T2&& vec2)
    {
        using RawT = std::remove_cvref_t<T1>;
        using ReturnT = std::remove_cvref_t<decltype(boost::pfr::get<0>(vec1))>;
        ReturnT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, [&ret](auto& field1, auto& field2)
                                                { ret += field1 * field2; });
        return ret;
    }

    template <Struct T> constexpr auto length(T&& vec)
    {
        if constexpr (std::is_integral_v<decltype(boost::pfr::get<0>(vec))>)
        {
            return ::solar::const_expr::sqrt(float(dot(vec, vec)));
        }
        else
        {
            return ::solar::const_expr::sqrt(dot(vec, vec));
        }
    }

    template <Struct T> constexpr auto rlength(T&& vec)
    {
        if constexpr (std::is_integral_v<std::remove_cvref_t<decltype(boost::pfr::get<0>(vec))>>)
        {
            return ::solar::const_expr::invSqrt(float(dot(vec, vec)));
        }
        else
        {
            return ::solar::const_expr::invSqrt(dot(vec, vec));
        }
    }

    template <Struct T> constexpr auto normalize(T&& vec) -> std::remove_cvref_t<T>
    {
        auto inv_length = ::solar::const_expr::invSqrt(dot(vec, vec));
        return multiply(vec, inv_length);
    }

    template <Struct T> constexpr auto floor(T&& vec) -> std::remove_cvref_t<T>
    {
        if constexpr (std::is_integral_v<std::remove_cvref_t<decltype(boost::pfr::get<0>(vec))>>)
        {
            return vec;
        }
        else
        {
            using RawT = std::remove_cvref_t<T>;
            RawT ret{};
            forEach<boost::pfr::tuple_size_v<RawT>>(vec, ret, [](auto& field, auto& ret_field)
                                                    { ret_field = std::floor(field); });
            return ret;
        }
    }

    template <Struct T1, Struct T2>
    constexpr auto reflect(T1&& i, T2&& n) -> std::remove_cvref_t<T1>
    {
        // return i - 2.0F * n * dot(n, i);
        return minus(i, multiply(multiply(n, decltype(boost::pfr::get<0>(i))(2.0)), dot(i, n)));
    }

    template <Struct T> constexpr auto abs(T&& vec) -> std::remove_cvref_t<T>
    {
        if constexpr (std::is_unsigned_v<std::remove_cvref_t<decltype(boost::pfr::get<0>(vec))>>)
        {
            return vec;
        }
        else
        {
            using RawT = std::remove_cvref_t<T>;
            RawT ret{};
            forEach<boost::pfr::tuple_size_v<RawT>>(vec, ret, [](auto& field, auto& ret_field)
                                                    { ret_field = std::abs(field); });
            return ret;
        }
    }

    template <Struct T1, Struct T2>
    constexpr auto max(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = std::max(field1, field2); });
        return ret;
    }

    template <Struct T1, Struct T2>
    constexpr auto min(T1&& vec1, T2&& vec2) -> std::remove_cvref_t<T1>
    {
        using RawT = std::remove_cvref_t<T1>;
        RawT ret{};
        forEach<boost::pfr::tuple_size_v<RawT>>(vec1, vec2, ret,
                                                [](auto& field1, auto& field2, auto& ret_field)
                                                { ret_field = std::min(field1, field2); });
        return ret;
    }

} // namespace solar::internal::vector
